use std::net::SocketAddr;
use std::sync::Arc;

use bytes::Bytes;
use http_body_util::{BodyExt, Full};
use hyper::body::Incoming;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Method, Request, Response, StatusCode};
use hyper_util::rt::tokio::TokioIo;
use serde::Deserialize;
use tokio::net::TcpListener;
use tracing::{info, warn};

use super::{Command, CommandBroadcaster};

#[derive(Debug, Deserialize)]
struct CommandRequest {
    #[serde(rename = "cmd")]
    command: String,
}

/// Service for handling and broadcasting commands.
pub struct CommandService {
    broadcaster: Arc<CommandBroadcaster>,
}

impl CommandService {
    /// Creates a new CommandService instance with a broadcaster.
    pub fn new(broadcaster: Arc<CommandBroadcaster>) -> Self {
        Self { broadcaster }
    }

    /// Runs the command service on the specified address.
    pub async fn run(
        &self,
        addr: SocketAddr,
    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        info!("🚀 Starting Command Service on http://{}", addr);
        let listener = TcpListener::bind(addr).await?;
        let broadcaster = self.broadcaster.clone();
        loop {
            let (stream, _) = listener.accept().await?;
            let io = TokioIo::new(stream);
            let broadcaster = broadcaster.clone();
            tokio::spawn(async move {
                let service = service_fn(move |req: Request<Incoming>| {
                    let broadcaster = broadcaster.clone();
                    async move {
                        Ok::<_, std::convert::Infallible>(handle_request(req, broadcaster).await)
                    }
                });
                let _ = http1::Builder::new().serve_connection(io, service).await;
            });
        }
    }
}

/// Handles incoming HTTP requests for commands.
async fn handle_request(
    req: Request<Incoming>,
    broadcaster: Arc<CommandBroadcaster>,
) -> Response<Full<Bytes>> {
    if req.method() != Method::POST {
        return Response::builder()
            .status(StatusCode::METHOD_NOT_ALLOWED)
            .body(Full::from(Bytes::from_static(b"method not allowed")))
            .unwrap();
    }

    let path = req.uri().path();
    if path != "/api/command" {
        return Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(Full::from(Bytes::from_static(b"not found")))
            .unwrap();
    }

    let (_, body) = req.into_parts();
    let Ok(collected) = body.collect().await else {
        return Response::builder()
            .status(StatusCode::BAD_REQUEST)
            .body(Full::from(Bytes::from_static(b"invalid body")))
            .unwrap();
    };
    let bytes = collected.to_bytes();
    let parsed: Result<CommandRequest, _> = serde_json::from_slice(bytes.as_ref());
    let Ok(body) = parsed else {
        return Response::builder()
            .status(StatusCode::BAD_REQUEST)
            .body(Full::from(Bytes::from_static(b"invalid json")))
            .unwrap();
    };

    match body.command.to_ascii_lowercase().as_str() {
        "linkup" => {
            info!(target: "command::service", "📡 Received command: LinkUp");
            let _ = broadcaster.send_command(Command::LinkUp);
        }
        "linkdown" => {
            info!(target: "command::service", "📡 Received command: LinkDown");
            let _ = broadcaster.send_command(Command::LinkDown);
        }
        unknown => {
            warn!(target: "command::service", "❌ Unknown command: {}", unknown);
            return Response::builder()
                .status(StatusCode::BAD_REQUEST)
                .body(Full::from(Bytes::from_static(b"unknown command")))
                .unwrap();
        }
    }

    Response::builder()
        .status(StatusCode::OK)
        .header(hyper::header::CONTENT_TYPE, "application/json")
        .body(Full::from(Bytes::from_static(b"{\"status\":\"ok\"}")))
        .unwrap()
}
